import { defineComponent, PropType, ref, VNodeChild, ExtractPropTypes, isVNode, cloneVNode, withDirectives } from 'vue'
import { Arrayable, isNumber } from '@vueuse/core'
import type { Moment, MomentInput } from 'moment'
import { castArray } from '../_util/_'

import BorderLight from '../border-light'

import Scroll from './Scroll'
import { filterEmpty } from '../_util/props-util'
import { isArray } from '@vue/shared'

const props = {
  prefixCls: String,
  start: Object as PropType<Moment>,
  modelValue: [Object, Array] as PropType<Arrayable<Moment>>,
  rangeValue: Array as PropType<Moment[]>,
  cols: Number,
  limit: Array as PropType<Moment[]>,
  //
  disabled: Function as PropType<(date: Moment) => boolean>,
  add: Function as PropType<(date: Moment, amount: number) => Moment>,
  diff: Function as PropType<(date1: Moment, date2: Moment) => number>,
  isBefore: Function as PropType<(date: Moment, date2: Moment) => boolean>,
  isAfter: Function as PropType<(date: Moment, date2: Moment) => boolean>,
  isSame: Function as PropType<(date: Moment, date2: MomentInput) => boolean>,
  group: Function as PropType<(date: Moment) => Moment>,
  isBeforeGroup: Function as PropType<(date: Moment, date2: Moment) => boolean>,
  isAfterGroup: Function as PropType<(date: Moment, date2: Moment) => boolean>,
  addGroup: Function as PropType<(date: Moment, amount: number) => Moment>,
  //
  cell: Function as PropType<(date: Moment) => VNodeChild>,
  onSelect: Function as PropType<(date: Moment) => void>,
  onHover: Function as PropType<(date: Moment) => void>,
  onGroupChange: Function as PropType<(date: Moment) => void>
}

export type GridProps = ExtractPropTypes<typeof props>

export default defineComponent({
  props,
  emits: ['update:modelValue'],
  setup(props, { slots, emit, expose }) {
    function onSelect(date: Moment) {
      emit('update:modelValue', date)
      props.onSelect?.(date)
    }

    // =============================================== scroll =====================================================

    const scrollEl = ref<typeof Scroll>()
    const group = ref(props.group(castArray(props.modelValue)[0] ?? props.add(props.start, props.cols - 1)))

    function onScroll(e: { y: number }) {
      const amount = (scrollEl.value.calc(e.y + 120).startI as number) * props.cols
      group.value = props.group(props.add(props.start, amount))
      props.onGroupChange?.(group.value)
    }
    function scrollIndex(index: number, t?: number) {
      scrollEl.value.scrollIndex(Math.floor(index / props.cols), t)
    }
    function scroll(date: Moment, t = 200) {
      const diff = props.diff(props.group(date), props.start)
      scrollIndex(diff, t)
    }
    const prev = () => scroll(props.addGroup(group.value, -1))
    const next = () => scroll(props.addGroup(group.value, 1))

    expose({
      prev,
      next,
      scroll,
      scrollIndex
    })

    // =============================================== render =====================================================

    function genRow({ index }: { index: number }) {
      const { prefixCls, cols, rangeValue: range } = props
      const startI = index * cols
      const endI = startI + cols
      const val = castArray(props.modelValue ?? props.rangeValue)
      const cells = Array(cols)
      for (let i = startI; i < endI; i++) {
        const date = props.add(props.start, i)
        const disabled = props.disabled?.(date)
        const isPrevGroup = props.isBeforeGroup?.(date, group.value)
        const isNextGroup = props.isAfterGroup?.(date, group.value)
        const selected = val.some(e => props.isSame?.(e, date))
        const inRange = range?.length == 2 && props.isAfter?.(date, range[0]) && props.isBefore?.(date, range[1])

        const cls = {
          [`${prefixCls}_cell`]: true,
          [`${prefixCls}_cell-disabled`]: disabled,
          [`${prefixCls}_cell-selected`]: selected,
          [`${prefixCls}_cell-in-range`]: inRange,
          [`${prefixCls}_cell-prev`]: isPrevGroup,
          [`${prefixCls}_cell-next`]: isNextGroup,
          [`${prefixCls}_cell-this`]: props.isSame?.(date, new Date())
        }

        let child = (props.cell || slots.default)(date)
        if (!isVNode(child)) {
          if (isArray(child)) {
            child = filterEmpty(child)
            child = child.length == 1 ? child[0] : <div>{child}</div>
          } else {
            child = <div>{child}</div>
          }
        }

        if (!isVNode(child)) {
          throw new Error('child in genRow is not a VNode')
        }

        const cellProps = {
          key: date.toString(),
          role: 'gridcell',
          'aria-selected': selected,
          title: date.toString(),
          class: cls,
          onClick: () => onSelect(date),
          onMouseenter: () => props.onHover?.(date)
        }

        child = cloneVNode(child, cellProps)
        child = withDirectives(child, [[BorderLight, { borderGradientSize: 50, bgColor: 'transparent', borderColor: '#80808060' }]])
        cells[i - startI] = child
      }

      return cells
    }

    return () => {
      const style = `display: grid; grid-template-columns: repeat(${props.cols}, 1fr)`
      return (
        <Scroll ref={scrollEl} style={style} onScroll={onScroll}>
          {{ default: genRow }}
        </Scroll>
      )
    }
  }
})
